package session

import (
	"database/sql"
	"fmt"
	"gitee.com/zhendliu/sorm/clause"
	"gitee.com/zhendliu/sorm/log"
	"gitee.com/zhendliu/sorm/parser"
	"github.com/pkg/errors"
	"reflect"
)

func (s *Session) Create(values interface{}) (int64, error) {
	defer s.Clear()

	err := s.buildInsertClause(values)
	if err != nil {
		return 0, err
	}

	results, err := s.executeInsertClauses()
	if err != nil {
		return 0, err
	}

	rowsAffected, _ := s.sumRowsAffected(results)

	if rowsAffected != 0 {
		return rowsAffected, nil
	}

	return 0, errors.New("没有找到对应的表")
}

func (s *Session) buildInsertClause(values interface{}) error {
	el := reflect.ValueOf(values).Elem()
	switch el.Kind() {
	case reflect.Slice:
		return s.buildSliceInsertClause(values, el)
	case reflect.Struct:
		return s.buildSingleInsertClause(values)
	default:
		return fmt.Errorf("unsupported type %T", values)
	}
}

func (s *Session) buildSliceInsertClause(values interface{}, val reflect.Value) error {

	//val := reflect.ValueOf(values).Elem()
	if val.Len() == 0 {
		return nil
	}
	var (
		tableName string
		err       error
	)
	s.Model(reflect.New(val.Index(0).Type()).Interface())
	if s.refTable != nil && s.refTable.Auto != "" {
		s.Omit(s.refTable.Auto)
	}
	getField := parser.GetField(s.refTable.FieldNames, s.selectField, s.omitField)
	table := s.RefTable()
	lenth := val.Len()

	if table.SubTableFile != "" {
		includeField := false
		for i := range getField {
			if getField[i] == table.SubTableFile {
				includeField = true
			}
		}
		if !includeField {
			return errors.New("分表模式下，必须含有分表字段")
		}
	}

	for i := 0; i < lenth; i++ {
		value := val.Index(i).Addr().Interface()
		val := reflect.Indirect(reflect.ValueOf(value))

		if table.SubTableFile != "" {
			v := parser.GetValueTag(val, value, table.SubTableFile)
			tableName, err = s.GetTableFromTableAndSplitFiled(table.Name, v)
			if err != nil {
				return errors.Wrap(err, "获取表名失败")
			}
		} else {
			tableName = table.Name
		}

		getRecordValues := table.RecordValuesCustom(val, value, s.selectField, s.omitField)

		if cla, ok := s.tableClause[tableName]; ok {
			cla.Set(clause.VALUES, getRecordValues)
		} else {
			cla = clause.Clause{}
			cla.Set(clause.INSERT, tableName, getField)
			cla.Set(clause.VALUES, getRecordValues)
			s.tableClause[tableName] = cla
		}
	}

	return nil
}

func (s *Session) buildSingleInsertClause(values interface{}) error {
	var (
		tableName string
		err       error
	)
	s.Model(values)

	if s.refTable != nil && s.refTable.Auto != "" {
		s.Omit(s.refTable.Auto)
	}

	table := s.RefTable()
	val := reflect.Indirect(reflect.ValueOf(values))

	if table.SubTableFile != "" {
		v := parser.GetValueTag(val, values, table.SubTableFile)
		tableName, err = s.GetTableFromTableAndSplitFiled(table.Name, v)
		if err != nil {
			log.Infof("获取表名失败")
			return errors.New("获取表名失败")
		}
	} else {
		tableName = table.Name
	}

	getField := parser.GetField(table.FieldNames, s.selectField, s.omitField)
	if table.SubTableFile != "" {
		includeField := false
		for i := range getField {
			if getField[i] == table.SubTableFile {
				includeField = true
			}
		}
		if !includeField {
			return errors.New("分表模式下，必须含有分表字段")
		}
	}

	getRecordValues := table.RecordValuesCustom(val, values, s.selectField, s.omitField)

	if cla, ok := s.tableClause[tableName]; ok {
		cla.Set(clause.VALUES, getRecordValues)
	} else {
		cla = clause.Clause{}
		cla.Set(clause.INSERT, tableName, getField)
		cla.Set(clause.VALUES, getRecordValues)
		s.tableClause[tableName] = cla
	}

	return nil
}

func (s *Session) executeInsertClauses() ([]sql.Result, error) {

	if s.tableClause == nil {
		return nil, nil
	}
	results := []sql.Result{}
	for tname, cla := range s.tableClause {
		sql, vars := cla.Build(clause.INSERT, clause.VALUES)
		tname = parser.GetTableName(tname)
		db, ok := s.dbs[tname]
		if !ok {
			db = s.db
		}
		result, err := s.exec(s.ctx, db.Db, db.Dia, sql, vars)
		if err != nil {
			return nil, errors.Wrap(err, "insert error")
		}
		results = append(results, result)
	}

	return results, nil
}

func (s *Session) sumRowsAffected(results []sql.Result) (int64, int64) {
	var rows, lastId int64
	for _, result := range results {
		if r, err := result.RowsAffected(); err == nil {
			rows += r
		}
		id, err := result.LastInsertId()
		if err == nil {
			lastId = id
		}
	}
	return rows, lastId
}
